package cn.uncode.dal.router;

import cn.uncode.dal.core.BaseDTO;
import cn.uncode.dal.criteria.Criterion;
import cn.uncode.dal.criteria.Model;
import cn.uncode.dal.criteria.QueryCriteria;
import cn.uncode.dal.criteria.QueryCriteria.Criteria;
import cn.uncode.dal.router.strategy.ShardingStrategy;
import cn.uncode.dal.router.strategy.ShardingStrategyFactory;
import cn.uncode.dal.utils.IDGenerateUtils;
import cn.uncode.dal.utils.ShardsUtils;

import org.springframework.core.NamedThreadLocal;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListSet;

public class TableShardingRouter {
	
	
	
    private static final ConcurrentMap<String, TableRouter> cache = new ConcurrentHashMap<String, TableRouter>();
	private static final ThreadLocal<Set<Long>> IDS = new NamedThreadLocal<Set<Long>>("sharding id"){
		@Override
		protected Set<Long> initialValue() {
			return new ConcurrentSkipListSet<Long>();
		}
	};

    public static void setTableRouter(String table, TableRouter tableRouter){
    	cache.putIfAbsent(table, tableRouter);
    }
    
    public static boolean containsTable(String table){
    	return cache.containsKey(table);
    }
    
    
    
    public static String getShardingTableById(String table, long id){
    	TableRouter tableRouter = cache.get(table);
    	String tableName = null;
    	if(null != tableRouter){
    		if(SharingType.CUSTOM.equals(tableRouter.getSharingType())){
    			ShardingStrategy shardingStrategy = ShardingStrategyFactory.loadStrategy(tableRouter);
    			String ttable = shardingStrategy.selectShardFromId(tableRouter.getTableName(), id);
    			if(null != ttable){
    				tableName = ttable;
    			}
    		}else{
    			for(Range range:tableRouter.getRanges()){
    	    		if(SharingType.RANGE.equals(tableRouter.getSharingType())){
        				if(range.getStart()<=id && id <= range.getEnd()){
        					tableName = range.getTableName();
        					break;
        				}
    	    		}else if(SharingType.HASH.equals(tableRouter.getSharingType())){
    	    			long hash = (id % 10000000L) / 10000;
    	    			if(range.getHashs().contains(String.valueOf(hash))){
    	    				tableName = range.getTableName();
        					break;
    	    			}
    	    		}
        		}
    		}
    	}
    	return tableName;
    }
    
    public static List<String> getShardingTables(QueryCriteria queryCriteria){
    	TableRouter tableRouter = cache.get(queryCriteria.getTable());
    	List<String> tables = new ArrayList<>();
    	if(null != tableRouter){
    		if(SharingType.RANGE.equals(tableRouter.getSharingType())){
    			List<Range> ranges = tableRouter.getRanges();
    			if(ranges != null){
    				for(Range range:ranges){
    					tables.add(range.getTableName());
    				}
    			}
    		}else if(SharingType.HASH.equals(tableRouter.getSharingType())){
    			List<String> hashs = new ArrayList<String>();
    			if(queryCriteria.getOredCriteria() != null && queryCriteria.getOredCriteria().size() > 0){
    				for(Criteria criteria:queryCriteria.getOredCriteria()){
    					for(Criterion criterion:criteria.getCriteria()){
    						if(criterion.getColumn().equals(tableRouter.getFieldName())){
    							hashs.add(String.valueOf(criterion.getValue()));
    						}
    					}
    				}
    			}
    			if(hashs.size() > 0){
    				for(String hash:hashs){
    					for(Range range:tableRouter.getRanges()){
    						if(range.getHashs().contains(hash)){
    							tables.add(range.getTableName());
    						}
    					}
    				}
    			}else{
    				if(tableRouter.getRanges() != null){
        				for(Range range:tableRouter.getRanges()){
        					tables.add(range.getTableName());
        				}
        			}
    			}
    		}else if(SharingType.CUSTOM.equals(tableRouter.getSharingType())){
    			Object shardingSigal = null;
				List<Criteria> criterias = queryCriteria.getOredCriteria();
				if (criterias != null && criterias.size() > 0) {
					for (Criteria criteria : criterias) {
						if (criteria.isValid()) {
							List<Criterion> criterions = criteria.getCriteria();
							for (Criterion criterion : criterions) {
								if (criterion.getColumn().equals(tableRouter.getFieldName())) {
									shardingSigal = criterion.getValue();
									if(shardingSigal != null){
										break;
									}
								}
							}
						}
						if(shardingSigal != null){
							break;
						}
					}
			    }
				ShardingStrategy shardingStrategy = ShardingStrategyFactory.loadStrategy(tableRouter);
				if(shardingSigal != null){
					String table = shardingStrategy.selectShardFromField(tableRouter.getTableName(), shardingSigal);
					tables.add(table);
				}else{
	    			String[] arr = shardingStrategy.selectAllShard(tableRouter.getTableName());
	    			tables.addAll(Arrays.asList(arr));
				}
    		}
    	}
    	return tables;
    }
    
    /**
     * 对外id生成暴露方法
     *<pre>
     * List<User> list = new ArrayList<User>();
     * TableShardingRouter.generateShardingId(list);
     * dal.insertList(list);
     * </pre>
     * @param objList 插入类
     */
    public static void generateShardingId(List<Object> objList){
    	if(objList != null) {
    		for(Object obj:objList) {
    			generateShardingId(obj);
    		}
    	}
    }
    
    /**
     * 对外id生成暴露方法
     * <pre>
     * User user = new User();
     * user.setName("juny");
     * TableShardingRouter.generateShardingId(user);
     * dal.insert(user);
     * </pre>
     * @param obj 插入类
     */
    public static void generateShardingId(Object obj){
    	Model insertObj = null;
    	if(obj instanceof Model == false) {
    		insertObj = new Model(obj);
    		generateShardingId(insertObj);
    		Method method = ReflectionUtils.findMethod(obj.getClass(), "setId", Long.class);
    		if(method != null) {
    			try {
					ReflectionUtils.invokeJdbcMethod(method, obj, insertObj.getSinglePrimaryKey());
				} catch (SQLException e) {
					e.printStackTrace();
				}
    		}
    	}
    }
    
    public static void generateShardingId(Model model){
    	long id = generateShardingId(model.getTableName(), model.getContent());
    	model.setSinglePrimaryKey(id);
    }
    
    public static long generateShardingId(String tableName, Map<String, Object> content){
    	TableRouter tableRouter = cache.get(tableName);
    	if(null != tableRouter){
    		Long id = (Long) ShardsUtils.getPrimaryKeyFromContent(content);
    		if(id != null && IDS.get() !=null && IDS.get().contains(id)){
    			return id;
    		}else{
    			if(SharingType.RANGE.equals(tableRouter.getSharingType())){
    				id = IDGenerateUtils.getId();
        		}else if(SharingType.HASH.equals(tableRouter.getSharingType())){
        			if(content == null || !content.containsKey(tableRouter.getFieldName())){
        				throw new RuntimeException("散列依据的字段不能为空");
        			}
        			Object hashVal = content.get(tableRouter.getFieldName());
        			id = IDGenerateUtils.getId(Long.valueOf(String.valueOf(hashVal)));
        		}else if(SharingType.CUSTOM.equals(tableRouter.getSharingType())){
        			ShardingStrategy shardingStrategy = ShardingStrategyFactory.loadStrategy(tableRouter);
        			id = shardingStrategy.createShardId(tableRouter.getFieldName(), content);
        		}
				 IDS.get().add(id);
				 content.put(BaseDTO.ID, id);
    			return id;
    		}
    	}
    	return IDGenerateUtils.getId();
    }
    
    
    public static Set<String> getIndexTableField(String table){
    	TableRouter tableRouter = cache.get(table);
    	if(tableRouter != null && tableRouter.hasIndexTableFields()){
    		return tableRouter.getIndexTableFields();
    	}
    	return null;
    }

}
